package javacl.impl;

import static gdts.common.logger.SimpleLogger.log;
import static java.lang.System.nanoTime;
import static org.bridj.Pointer.pointerToFloats;
import gdts.common.data.VarType;
import gdts.common.data.type.VArray;
import gdts.common.data.type.VFloat;
import gdts.common.data.type.VInteger;
import gdts.common.data.type.VString;
import gdts.common.module.ModuleBase;
import gdts.common.module.ModuleType;
import gdts.common.module.annotation.Command;
import gdts.common.module.annotation.Module;
import opencl.IKernelCompiler;
import opencl.impl.JavaCLKernelCompiler;

import org.bridj.Pointer;

import com.nativelibs4java.opencl.CLBuffer;
import com.nativelibs4java.opencl.CLContext;
import com.nativelibs4java.opencl.CLEvent;
import com.nativelibs4java.opencl.CLKernel;
import com.nativelibs4java.opencl.CLMem.Usage;
import com.nativelibs4java.opencl.CLQueue;
import com.nativelibs4java.opencl.JavaCL;

@Module(id = "JavaCLModule", version = "1.0.0", type = ModuleType.BOTH)
public class JavaCLModule extends ModuleBase {

    private IKernelCompiler<CLKernel> kernelCompiler = new JavaCLKernelCompiler();
    private CLContext context = JavaCL.createBestContext();
    private CLKernel kernel;
    private int parametersCount;
    private CLBuffer<Float> out;
    private VString lastDataFile = new VString("");

    @Command(id = "compile")
    public void compile(VString kernelDataFile) {
        if (lastDataFile.equals(kernelDataFile) && kernel != null) {
            return;
        }
        lastDataFile = kernelDataFile;
        log("compiling kernel " + kernelDataFile.toString());
        kernel = kernelCompiler.compile(kernelDataFile.toString());
        log("kernel " + kernelDataFile.toString() + " compiled!");
    }

    @Command(id = "setParameters")
    public void setParameters(VArray firstArray, VArray secondArray) {
        if (kernel == null) {
            log("Kernel is not set");
            return;
        }
        log("Setting parameters...");
        parametersCount = firstArray.size();

        Pointer<Float> firstArrayPointer = pointerToFloats(firstArray.toFloatArray());
        Pointer<Float> secondArrayPointer = pointerToFloats(secondArray.toFloatArray());

        CLBuffer<Float> arrayA = context.createFloatBuffer(Usage.Input, firstArrayPointer);
        CLBuffer<Float> arrayB = context.createFloatBuffer(Usage.Input, secondArrayPointer);
        out = context.createFloatBuffer(Usage.Output, parametersCount);

        kernel.setArgs(arrayA, arrayB, out, parametersCount);
        log("Setting parameters successful!");
    }

    @Command(id = "execute")
    public void execute(VFloat result) {
        CLQueue queue = context.createDefaultQueue();

        log("Starting execution of kernel...");
        long currTime = nanoTime();
        CLEvent addEvt = kernel.enqueueNDRange(queue, new int[] { parametersCount });
        currTime = nanoTime() - currTime;

        log("Execution finished, took: " + currTime / 1000000.0f + " ms");

        Pointer<Float> outPtr = out.read(queue, addEvt);
        float value = 0;
        for (int i = 0; i < parametersCount; i++) {
            value = value + outPtr.get(i);
        }
        result.setValue(new VFloat(new Double(value)));
        log("Result: " + result.doubleValue());
    }

    @Command(id = "sliceArrays")
    public void sliceArrays(VInteger count, VArray firstArray, VArray secondArray, VArray vectorsFromFirst,
            VArray vectorsFromSecond) {

        long slaveSamplesCount = firstArray.size() / count.longValue();

        for (int j = 0; j < count.longValue(); j++) {
            VArray firstVector = new VArray(VarType.FLOAT);
            VArray secondVector = new VArray(VarType.FLOAT);
            for (int i = (int) (j * slaveSamplesCount); i < (int) j * slaveSamplesCount + slaveSamplesCount; i++) {
                firstVector.add(firstArray.getElementAt(i));
                secondVector.add(secondArray.getElementAt(i));
            }
            vectorsFromFirst.add(firstVector);
            vectorsFromSecond.add(secondVector);
        }
    }

    @Command(id = "addToArray")
    public void addToArray(VArray floats, VFloat result) {
        if (floats == null) {
            floats = new VArray(VarType.FLOAT);
        }
        floats.add(result);
    }

    @Command(id = "addFloats")
    public void addFloats(VArray first, VArray second, VFloat result) {
        result = new VFloat(0);
        for (int i = 0; i < first.size(); i++) {
            result.add((VFloat) first.getElementAt(i));
            result.add((VFloat) second.getElementAt(i));
        }
    }

    @Command(id = "combineResults")
    public void combineResults(VArray floats, VFloat result) {
        for (int i = 0; i < floats.size(); i++) {
            result.add((VFloat) floats.getElementAt(i));
        }
    }

    public CLKernel getKernel() {
        return kernel;
    }
}